System V的ABI的Function Calling Sequence(函数调用顺序)

Author Avatar
Magicmanoooo 3月 09, 2019
  • 在其它设备中阅读本文章

1. Register and Stack Frame(寄存器和帧栈)

AMD64体系提供16个64位通用寄存器。此外,该体系还提供16个SSE寄存器(每一个占128位)以及8个X87 floating point register(浮点寄存器,每一个占80位)。每一个X87浮点寄存器在MMX/3DNow! 模型中也许代表一个64位寄存器。这些寄存器相对于一个正在运行的程序中的所有procedures而言都是全局的。

%rbp,%rbx以及%r12%r15都属于calling function(调用函数),被调用的函数需要保存这些值。换言之,一个被调用的函数必须为caller(调用者)这些寄存器的值。剩下的寄存器属于被调用函数。如果一个调用函数想要通过函数调用来保存这类寄存器的值,它必须在其local stack frame(局部帧栈)中保存这一值。

2. The Stack Frame(帧栈)

除了寄存器之外,每一个函数还用一个run-time stack(运行栈)。该栈从高地址向下增长,图示为stack organization:

input arrgument area(输入参数区)的结尾应该与16位的边界进行对齐。换言之,当control被转换为function entry point(函数进入点)时,栈指针%rsp总是指向最近分配的帧栈的尾部。

Entry point

在CS中,entry point是控制从OS转换为计算机程序的地方,处理器从此处加载程序或者代码段并开始执行。在一些OS或编程语言中,initial entry不属于程序的一部分,而属于runtime library(运行时库),在这种情况下,程序在第一次载入的时,在做其他事之前首次会调用运行时库。当运行库返回时,实际的代码才会开始执行。这标志着从load time(装载期,或者动态链接期)向run time(运行期)的转变。

Parameter Passing(参数传递)

当参数值被计算之后,它们便被放置在寄存器中,或者被压入栈中。

定义:定义一些classes来classify arguments(区分参数)。这些classes对应于AMD64寄存器:

  • INTEGER
    这个class包含了符合通用寄存器中的integral types(整型)
  • SSE
    这个class包含了符合SSE寄存器的类型
  • SSEUP
    The class consists of types that fit into a SSE register and can be passed and returned in the most significant half of it.(这个class包含了满足SSE寄存器的类型,而且它可以以半个最重要的寄存器传递和返回)
  • X87X87UP
    这些classes包含通过x87 FPU(Floating Point Unit)返回的类型
  • COMPLEX_X87
    这个class包含了经由x87 FPU返回的类型
  • NO_CLASS
    此class被用来在算法中起初始化的作用。它将被用来进行padding(填充)和empty structure and unions(空结构体和空共用体)
  • MEMORY
    该class包含了在内存中经由stack传递和返回的类型

分类

每一个参数的大小都会被填充为8bit的整数倍

  • INTEGER class(包含signed和unsigned两种)
    • _Bool
    • char
    • short
    • int
    • long
    • long long
    • pointer
  • SSE class
    • float
    • double
    • _m64
  • _float128_m128被分为两部分。The least significant ones belong to class SSE, the most significant one to class SSEUP.
  • 64位参数类型long double的小数部分属于X87 class,16位指数加上6 bits填充的类型属于X87UP class
  • _int128类型的参数提供同INTEGER一样的操作,但通用寄存器不适用于它们,而是需要两个寄存器。故可以将_int128看成如下的实现:
typedef struct{
    long low,high;        
}_int128;

【注意】:除了存储在内存中且必须在16字节的边界上对齐的__int128类型参数外。

  • aggregate(structure and array,聚合类型)和union types:
    1. 如果对象的大小大于两个8字节,或者为C++中的non-POD structure或union,或者包含unaligned fields(未对齐的域),则以上类型属于MEMERY class
    2. 这两个8字节都将被初始化为NO_CLASS class
    3. 对象的每一个field(字段)都将进行递归地分类,以便总是考虑两个fields。resulting class按照8字节的field进行计算:
      (1) 如果两个classes相等, 则此class便是resulting class
      (2)如果一个为NO_CLASS,则resulting class便是另一个class
      (3)如果一个为MEMORY,则结果为MEMORY class
      (4)如果一个为INTEGER,则结果为INTEGER
      (5)如果一个为X87,X87UP class,则结果为MEMORY
      (6)其他class使用SSE
  1. 然后进行post merger cleanup(合并后清理)
    (1)如果一个class为MEMORY,则整个参数在内存中进行传递
    (2)如果SSEUP在SSE之后,则SSEUP将被转化为SSE

Passing

一旦参数被分类,则寄存器便被分配(按照从左到右的顺序),如下所示:

  1. 如果class是MEMORY,则将参数传递给stack
  2. 如果class为INTEGER,则下一个可用寄存器序列为%rdi%rsi%rdx%rcx%r8以及%r9
  3. 如果class为SSE,则下一个可用的SSE寄存器将被使用,顺序为从%xmm0%xmm7
  4. 如果class为SSEUP,则将8字节传递给最近使用的SSE寄存器的高半地址部分
  5. 如果class为X87,X87UP,则在内存中进行传递

如果没有可用的寄存器用于8字节的参数,则整个参数将被传递给stack;如果已经给这个参数的某些8字节分配了寄存器,则这些分配将被reverted(还原)

一旦寄存器被分配,则在内存中进行传递的参数将以逆序(即从右到左的顺序)压入stack中

Returning of Values(返回值)

返回值是根据以下算法完成的:

  1. 利用classification algorithm(分类算法)对返回值进行区分
  2. 如果类型拥有MEMORY class,则caller(调用者)为返回值提供空间,并且将此存储空间作为函数的第一个参数传递给%rdi。实际上,这个地址变成了“隐藏的”第一参数。返回时,%rax将包含caller在%rdi中传入的地址
  3. 如果class为INTEGER,则下一个可用的寄存器顺序%rax%rdx将被使用
  4. 如果class为SSE,则下个可用的寄存器序%xmm0%xmm1将被使用
  5. 如果class为SSEUP,则将8字节传递给最近使用的SSE寄存器的高半地址部分
  6. 如果class为X87,则在X87 stack的%st0处以80bits大小X87 number的形式返回该值
  7. 如果class为X87UP,则该值和前一个X87值在%st0处一同返回
  8. 如果class为COMPLEX_87,则该值的real part(实数部分)在%st0处返回,imaginary part(虚数部分)在%st1处返回